﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using AKalinovsky.Map.Core;
using AKalinovsky.Map.Geo;
using System;

namespace AKalinovsky.Map
{
    public class MapTileLayer : Panel
    {
        private readonly MultiScaleImage _msi;

        private const int _tileWidth = 0x100;
        private const int _tileHeight = 0x100;

        public TileSource TileSource
        {
            get { return (TileSource)GetValue(TileSourceProperty); }
            set { SetValue(TileSourceProperty, value); }
        }

        public static readonly DependencyProperty TileSourceProperty =
            DependencyProperty.Register("TileSource", typeof(TileSource), typeof(MapTileLayer), new PropertyMetadata(new OsmTileSource(), OnTileSourceChangedCallback));

        private static void OnTileSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MapTileLayer)d).OnTileSourceChanged(e);
        }

        private void OnTileSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            _msi.Source = new MapTileSource((TileSource)e.NewValue, _tileWidth, _tileHeight);
            SetCenter(Center, Zoom);
        }

        public double Zoom
        {
            get { return (double)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, value); }
        }

        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(double), typeof(MapTileLayer), new PropertyMetadata(1.0, OnZoomChangedCallback));

        private static void OnZoomChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MapTileLayer)d).OnZoomChanged(e);
        }

        private void OnZoomChanged(DependencyPropertyChangedEventArgs e)
        {
            SetCenter(Center, (double)e.NewValue);
        }

        public double Azimuth
        {
            get { return (double)GetValue(AzimuthProperty); }
            set { SetValue(AzimuthProperty, value); }
        }

        public static readonly DependencyProperty AzimuthProperty =
            DependencyProperty.Register("Azimuth", typeof(double), typeof(MapTileLayer), new PropertyMetadata(1.0, OnAzimuthChangedCallback));


        private static void OnAzimuthChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MapTileLayer)d).OnAzimuthChanged(e);
        }

        private void OnAzimuthChanged(DependencyPropertyChangedEventArgs e)
        {
            RotateTransform transform = _msi.RenderTransform as RotateTransform;
            if (transform != null)
            {
                transform.Angle = 360.0 - (double)e.NewValue;
            }
        }

        public Position Center
        {
            get { return (Position)GetValue(CenterProperty); }
            set { SetValue(CenterProperty, value); }
        }

        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Position), typeof(MapTileLayer), new PropertyMetadata(new Position(), OnCenterChangedCallback));

        private static void OnCenterChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MapTileLayer)d).OnCenterChanged(e);
        }

        private void OnCenterChanged(DependencyPropertyChangedEventArgs e)
        {
            SetCenter((Position)e.NewValue);
        }

        public void SetCenter(Position center, double zoom)
        {
            _msi.ViewportWidth = _msi.ActualWidth / (_tileWidth * Math.Pow(2.0, zoom));
            SetCenter(center);
        }

        public void SetCenter(Position center)
        {
            if (TileSource != null)
            {
                _msi.ViewportOrigin = new Point(TileSource.Transformation.GetTileX(center.Longitude) - _msi.ViewportWidth / 2,
                   TileSource.Transformation.GetTileY(center.Latitude) - _msi.ViewportWidth / _msi.ActualWidth * _msi.ActualHeight / 2);
            }
        }

        public MapTileLayer()
            : this(null)
        { }

        internal MapTileLayer(TileSource tileSource)
            : base()
        {
            Clip = new RectangleGeometry();
            _msi = new MultiScaleImage { UseSprings = false, SkipLevels = 20 };
            _msi.RenderTransform = new RotateTransform();
            _msi.RenderTransformOrigin = new Point(0.5, 0.5);
            _msi.IsHitTestVisible = false;
            Children.Add(_msi);

            TileSource = tileSource ?? new OsmTileSource();

            SizeChanged += MapTileLayer_SizeChanged;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            ((RectangleGeometry)Clip).Rect = new Rect(0.0, 0.0, availableSize.Width, availableSize.Height);
            double side = Math.Sqrt(availableSize.Height * availableSize.Height + availableSize.Width * availableSize.Width);
            _msi.Measure(new Size(side, side));
            return new Size();
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double side = Math.Sqrt(finalSize.Height * finalSize.Height + finalSize.Width * finalSize.Width);
            _msi.Arrange(new Rect((finalSize.Width - side) / 2, (finalSize.Height - side) / 2, side, side));
            return base.ArrangeOverride(finalSize);
        }

        void MapTileLayer_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetCenter(Center, Zoom);
        }
    }
}
